
namespace System.Linq.Expressions
{
    public class MathExpressionSimplifier : ExpressionVisitorEx
    {
        private static bool IsNumerical(object value)
        {
            return value is double
                   || value is float
                   || value is int
                   || value is short
                   || value is uint
                   || value is ushort
                   || value is byte
                   || value is sbyte
                   || value is long
                   || value is ulong;
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            var lv_Base = base.VisitBinary(b);

            b = lv_Base as BinaryExpression;
            if(b == null) return lv_Base;

            var l = b.Left as ConstantExpression;
            var r = b.Right as ConstantExpression;

            if(l != null && r != null)
            {

                if(IsNumerical(l.Value) && IsNumerical(r.Value))
                {
                    var lValue = (double)l.Value;
                    var rValue = (double)r.Value;

                    switch(b.NodeType)
                    {
                        case ExpressionType.Add:
                            return Expression.Constant(unchecked(lValue + rValue));
                        case ExpressionType.AddChecked:
                            return Expression.Constant(lValue + rValue);

                        case ExpressionType.Subtract:
                            return Expression.Constant(unchecked(lValue - rValue));
                        case ExpressionType.SubtractChecked:
                            return Expression.Constant(lValue - rValue);

                        case ExpressionType.Multiply:
                            return Expression.Constant(unchecked(lValue * rValue));
                        case ExpressionType.MultiplyChecked:
                            return Expression.Constant(lValue * rValue);

                        case ExpressionType.Divide:
                            return Expression.Constant(lValue / rValue);

                        case ExpressionType.GreaterThan:
                            return Expression.Constant(lValue > rValue);

                        case ExpressionType.GreaterThanOrEqual:
                            return Expression.Constant(lValue >= rValue);

                        case ExpressionType.LessThan:
                            return Expression.Constant(lValue < rValue);

                        case ExpressionType.LessThanOrEqual:
                            return Expression.Constant(lValue <= rValue);

                        case ExpressionType.Equal:
                            return Expression.Constant(Math.Abs(lValue - rValue) < double.Epsilon);

                        case ExpressionType.NotEqual:
                            return Expression.Constant(Math.Abs(lValue - rValue) > double.Epsilon);

                        default:
                            return lv_Base;
                    }
                }
            }



            return lv_Base;
        }

        protected override Expression VisitUnary(UnaryExpression u)
        {
            var lv_Base = base.VisitUnary(u);

            var unary = lv_Base as UnaryExpression;
            if(unary == null) return lv_Base;
            var operand = u.Operand as ConstantExpression;
            if(operand == null) return lv_Base;

            if(IsNumerical(operand.Value))
            {
                var value = (double)operand.Value;
                switch(lv_Base.NodeType)
                {
                    case ExpressionType.Negate:
                        return Expression.Constant(unchecked(-value));
                    case ExpressionType.NegateChecked:
                        return Expression.Constant(-value);
                    //case ExpressionType.Not:
                    //case ExpressionType.Convert:
                    //case ExpressionType.ConvertChecked:
                    //case ExpressionType.ArrayLength:
                    //case ExpressionType.Quote:
                    //case ExpressionType.TypeAs:
                    default:
                        return lv_Base;
                }
            }

            return lv_Base;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            var lv_Base = base.VisitMethodCall(m);

            var call = lv_Base as MethodCallExpression;
            if(call == null || call.Object != null && !(call.Object is ConstantExpression)) return lv_Base;
            var method = call.Method;
            var obj = (ConstantExpression)call.Object;

            var pp = call.Arguments.ToArray();
            if(!pp.All(p => p is ConstantExpression && IsNumerical(((ConstantExpression)p).Value)))
                return lv_Base;

            var vv = pp.Cast<ConstantExpression>().Select(p => p.Value).ToArray();
            var result = method.Invoke(obj == null ? null : obj.Value, vv);
            return Expression.Constant(result);
        }
    }
}